<?php
/**
 * Author: DefinitlyEvil
 * Created at: 2020/4/14 20:04
 */

namespace App\PublicAPI;


use App\Controller;
use App\Tools\ImageCode;
use App\Tools\Pattern;
use App\Tools\SMS;
use Gregwar\Captcha\CaptchaBuilder;

class CaptchaController extends Controller
{
    public $needAuth = false;

    public static function register(\FastRoute\RouteCollector $r){
        $r->post('/imageCode', [self::class, 'imageCode']);
        $r->post('/sendSMS', [self::class, 'sendSMS']);
    }

    public function imageCode() {
        $id = sha1(time() . ':image:' . random_bytes(16));
        $code = substr(sha1(time() . 'blah' . random_bytes(16)), 0, 4);

        $captcha = new CaptchaBuilder($code);
        $captcha->build();
        $data = base64_encode($captcha->get());

        $r = $this->getRedis();
        $r->setex(sprintf('image-code:%s', $id), 5*60, $code);

        return [['id' => $id, 'image' => $data], true];
    }

    public function sendSMS($params = []) {
        if(!isset($params['p']) or !isset($params['ci']) or !isset($params['c'])) {
            return ['invalid params', false];
        }
        if(!is_string($params['p']) or !Pattern::matches('/^[0-9]{11}$/', $params['p'])) return ['bad phone', false];
        if(!ImageCode::verify($this->getRedis(), $params['ci'], $params['c'])) return ['bad code', false];

        $phone = $params['p'];

        $pdo = $this->getPdo();
        // check already registered or not
        {
            $stm = $pdo->prepare('SELECT `id` FROM `accounts` WHERE `phone`=:p');
            $stm->bindParam(':p', $phone);
            if($stm->execute() === false) return ['database error (checking existing accounts)', false];
            if($stm->fetch(\PDO::FETCH_ASSOC) !== false) {
                return ['that phone number has already been registered', false];
            }
        }
        // check rate
        {
            $stm = $pdo->prepare('SELECT COUNT(`id`) AS `sent` FROM `phone_codes` WHERE `phone`=:p AND `send_time`+86400>UNIX_TIMESTAMP()');
            $stm->bindParam(':p', $phone);
            if($stm->execute() === false) return ['database error / 1 (checking sent codes)', false];
            $rs = $stm->fetch(\PDO::FETCH_ASSOC);
            if($stm->execute() === false) return ['database error / 2 (checking sent codes)', false];
            if($rs['sent'] > 3) {
                return ['daily code send limit for your phone has reached', false];
            }
        }
        // redis
        $redis = $this->getRedis();
        {
            $redis_key = sprintf('phone-sms-code:%s', $phone);
            $redis_val = $redis->get($redis_key);
            if($redis_val !== false) {
                return ['you can only send sms per 60 secs', false];
            }
        }

        $digits = '';
        $sum = 0;
        for($i = 0; $i < 5; $i++) {
            $d = rand(0,9);
            $sum += $d;
            $digits .= $d;
        }

        $last_digit = 10-((($sum+3)%7)+1);
        $digits .= $last_digit;

        // save to db
        {
            $stm = $pdo->prepare('INSERT INTO `phone_codes` (`phone`,`send_time`,`code`) VALUES(:phone,UNIX_TIMESTAMP(),:code)');
            $stm->bindParam(':phone', $phone);
            $stm->bindParam(':code', $digits);
            if($stm->execute() === false or $stm->rowCount() <= 0) return ['sms code save fail', false];
            $smsId = intval($pdo->lastInsertId());
        }

        try {
            SMS::sendCode($phone, $digits);
        } catch (\Exception $ex) {
            return ['send sms fail: ' . $ex->getMessage(), false];
        }

        $redis->setex($redis_key, 60, '1');

        return [['sms' => $smsId], true];
    }
}